/**
 *  Copyright 2007-2008 University Of Southern California
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package edu.isi.pegasus.planner.client;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.DecimalFormat;
import java.text.NumberFormat;

import edu.isi.pegasus.common.logging.LogManager;
import edu.isi.pegasus.common.logging.LogManagerFactory;
import edu.isi.pegasus.common.util.FactoryException;
import edu.isi.pegasus.common.util.Version;
import edu.isi.pegasus.planner.common.PegasusProperties;
import gnu.getopt.Getopt;
import gnu.getopt.LongOpt;
import java.util.MissingResourceException;

/**
 * The interface which defines all the methods , any executable should implement.
 *
 * @author GAURANG MEHTA
 * @author KARAN VAHI
 * @version $Revision: 4665 $
 *
 */
public abstract class Executable {

    /**
     * The LogManager object which is used to log all the messages.
     *
     */
    protected LogManager mLogger ;

    /**
     * The object holding all the properties pertaining to Pegasus.
     */
    protected PegasusProperties mProps;

    /**
     * It stores the verison of the Griphyn Virtual Data System software.
     */
    protected String mVersion;

    /**
     * The error message to be logged.
     */
    protected String mLogMsg;
    
    /**
     * The command line options passed to the executable
     */
    
    private String[] commandLineOpts;
    
    /**
     * The default constructor.
     */
    public Executable(){
        this( null );
    }

    /**
     * The constructor which ends up initialising the PegasusProperties object.
     * 
     * @param logger  the logger to use. Can be null.
     */
    public Executable( LogManager logger ) {
    	mLogger = logger;
    }
    
    /**
     * Looks up for the conf property in the arguments passed to the executable
     * @param opts command line arguments passed to the executable
     * @param confChar the short option corresponding to the conf property
     * @return
     */
    protected String lookupConfProperty(String[] opts , char confChar){
    	LongOpt[] longOptions = new LongOpt[1 ];
    	longOptions[ 0 ] = new LongOpt( "conf", LongOpt.REQUIRED_ARGUMENT, null,confChar );
    	Getopt g = new Getopt("Executable", opts, confChar+":", longOptions, false);
	g.setOpterr(false);
    	String propertyFilePath = null;
    	int option = 0;
    	while ( ( option = g.getopt() ) != -1 ) {
    		if(option == confChar){
    			propertyFilePath = g.getOptarg();
    			break;
    		}
    	}
    	return propertyFilePath;
    }
    
    
    /**
     * Initialize the executable object 
     * @param opts  the command line argument passed by the user
     * @param confChar the short option corresponding the conf property.
     */
    protected void initialize(String[] opts , char confChar){
    	this.commandLineOpts = opts;
    	String propertyFile =lookupConfProperty(getCommandLineOptions(), confChar);
        mProps = PegasusProperties.getInstance(propertyFile);
        mVersion = Version.instance().toString();
        //setup logging before doing anything with properties
        try{
        	setupLogging( mLogger , mProps );
        }catch(IOException ioe){
        	throw new RuntimeException("Unable to initialize the logger " , ioe);
        }
        mLogMsg = new String();

        sanityCheckOnProperties( );
        loadProperties();
    }
    
    /**
     * Initialize the executable object 
     * @param opts the command line argument passed to the executable
     */
    
    protected void initialize(String[] opts) {
    	initialize(opts, 'c');
    }

    /**
     * Returns an error message that chains all the lower order error messages
     * that might have been thrown.
     *
     * @param e  the Exception for which the error message has to be composed.
     * 
     * @return  the error message.
     */
    public static String convertException( Exception e ){
        return Executable.convertException( e, LogManager.TRACE_MESSAGE_LEVEL );
    }
    /**
     * Returns an error message that chains all the lower order error messages
     * that might have been thrown.
     *
     * @param e  the Exception for which the error message has to be composed.
     * @param logLevel  the user specified level for the logger
     * 
     * @return  the error message.
     */
    public static String convertException( Exception e , int logLevel ){
        StringBuffer message = new StringBuffer();
        int i = 0;
        
        //check if we want to throw the whole stack trace
        if( logLevel >= LogManager.TRACE_MESSAGE_LEVEL ){
            //we want the stack trace to a String Writer.
            StringWriter sw = new StringWriter();
            e.printStackTrace( new PrintWriter( sw ) );
            
            return sw.toString();
        }
        
        //append all the causes
        for(Throwable cause = e; cause != null ; cause  = cause.getCause()){
            if( cause instanceof FactoryException ){
                //do the specialized convert for Factory Exceptions
                message.append(((FactoryException)cause).convertException(i));
                break;
            }
            message.append("\n [").append( Integer.toString(++i)).append("] ").
                    append(cause.getClass().getName()).append(": ").
                    append(cause.getMessage());

            //append just one elment of stack trace for each exception
            message.append( " at " ).append( cause.getStackTrace()[0] );
        }
        return message.toString();
    }

    /**
     * Sets up the logging options for this class. Looking at the properties
     * file, sets up the appropriate writers for output and stderr.
     * 
     * @param logger   the logger to use. Can be null.
	 * @param properties reference of pegasus properties object.
     */
    protected void setupLogging( LogManager logger , PegasusProperties properties  ) throws IOException{
        if( logger != null ){
            mLogger = logger;
            return;
        }
        
        //setup the logger for the default streams.
        mLogger = LogManagerFactory.loadSingletonInstance( properties );
        mLogger.logEventStart( "event.pegasus.planner", "planner.version", mVersion );

        //get the logging value set in properties
        String value = properties.getProperty("pegasus.log.*");

        //use defaults if nothing is set.
        if( value == null){
            mLogger.log("Logging to default streams",
                        LogManager.DEBUG_MESSAGE_LEVEL);
            return;
        }
        else{
            //take a backup of the log if required.
            File f = new File( value );
            File dir = f.getParentFile();
            String basename = f.getName();

            NumberFormat formatter = new DecimalFormat( "000" );
            File backupFile = null;
            //start from 000 onwards and check for existence
            for( int i = 0; i < 999 ; i++ ){
                StringBuffer backup = new StringBuffer();
                backup.append( basename ).append( "." ).append( formatter.format(i) );
                
                //check if backup file exists.
                backupFile = new File( dir, backup.toString() );
                if( !backupFile.exists() ){
                    break;
                }
            }
                        
            //log both output and error messages to value specified
            mLogger.setWriters(backupFile.getAbsolutePath ());
        }
    }



    /**
     * Loads all the properties that would be needed by the Toolkit classes.
     */
    public abstract void loadProperties();

    /**
     * This method is used to print the long version of the command.
     */
    public abstract void printLongVersion();

    /**
     * This is used to print the short version of the command.
     */
    public abstract void printShortVersion();

    /**
     * This function is passed command line arguments. In this function you
     * generate the valid options and parse the options specified at run time.
     */
    //public abstract void executeCommand(String[] args);

    /**
     * Generates an array of valid <code>LongOpt</code> objects which contain
     * all the valid options to the Executable.
     */
    public abstract LongOpt[] generateValidOptions();

    /**
     * Returns the version of the Griphyn Virtual Data System.
     */
    public String getGVDSVersion() {
        StringBuffer sb = new StringBuffer();
        sb.append( "Pegasus Release Version " ).append(mVersion);
        return sb.toString();
    }

    /**
     * Logs messages to the singleton logger.
     *
     * @param msg is the message itself.
     * @param level is the level to generate the log message for.
     */
    public void log( String msg, int level ){
        mLogger.log( msg, level );
    }



    /**
     * Get the value of the environment variable.
     *
     * @param envVariable   the environment variable whose value you want.
     *
     * @return  String corresponding to the value of the environment
     *          variable if it is set.
     *          null if the environment variable is not set
     */
    public String getEnvValue(String envVariable) {
        String value = null;
        value = System.getProperty(envVariable);
        return value;
    }
    
    /**
     * Returns the command line arguments passed to the executable
     * @return command line arguments passed to the executable
     */
    protected String[] getCommandLineOptions(){
    	String[] optsClone = new String[commandLineOpts.length];
		for(int i =0; i< commandLineOpts.length;i++){
			optsClone[i] = commandLineOpts[i];
		}
    	return optsClone;
    }

    /**
     * Does a sanity check on the properties to make sure that all the 
     * required properties are loaded.
     * 
     */
    protected void sanityCheckOnProperties() {
        // check required properties
        if ( mProps.getProperty( "pegasus.home.bindir" ) == null ) {
            throw new MissingResourceException( "The pegasus.home.bindir property was not set ",
                    "java.util.Properties", "pegasus.home.bindir" );
        }

        if ( mProps.getProperty( "pegasus.home.schemadir" ) == null ) {
            throw new MissingResourceException( "The pegasus.home.schemadir property was not set ",
                    "java.util.Properties", "pegasus.home.schemadir" );
        }

       if ( mProps.getProperty( "pegasus.home.sharedstatedir" ) == null ) {
            throw new MissingResourceException( "The pegasus.home.sharedstatedir property was not set ",
                    "java.util.Properties", "pegasus.home.sharedstatedir" );
        }

        if ( mProps.getProperty( "pegasus.home.sysconfdir" ) == null ) {
            throw new MissingResourceException( "The pegasus.home.sysconfdir property was not set ",
                    "java.util.Properties", "pegasus.home.sysconfdir" );
        }
    }

}
